package org.destinyshine.cricket.connector;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.destinyshine.cricket.utils.MultiMap;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletInputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.*;
import java.nio.ByteBuffer;
import java.security.Principal;
import java.util.*;

/**
 * Created by destinyliu on 2016/2/20.
 */
public class HttpServletRequestImpl implements HttpServletRequest {

    private final Logger logger = LogManager.getFormatterLogger();

    private final String requestURL;
    private String requestPath;

    private MultiMap<String, String> headersMap;

    private String method;

    private String protocol;

    private String serverName;

    private int serverPort;
    private String scheme;
    private String requestURI;
    private String queryString;

    private MultiMap<String, String> queryParameters;
    private ByteBuffer contentBytes;
    private ServletInputStream inputStream;
    private BufferedReader reader;

    public HttpServletRequestImpl(String[] requestInfos, Map<String, List<String>> headers) {
        this.method = requestInfos[0];
        this.requestPath = requestInfos[1];
        parseRequstPath();
        this.protocol = requestInfos[2];
        this.headersMap = new MultiMap<String, String>(headers) {
        };
        String host = getHeader("Host");
        if (host.indexOf(':') == -1) {
            this.serverName = host;
            this.serverPort = getDefaultServerPort();
        } else {
            String[] hostSplited = StringUtils.split(host, ':');
            this.serverName = hostSplited[0];
            this.serverPort = Integer.parseInt(hostSplited[1]);
        }
        this.scheme = "http";
        this.requestURL = scheme + "://" + host + requestPath;
    }

    /**
     * requestPath中有查询参数，需要解析。
     */
    private void parseRequstPath() {
        queryParameters = new MultiMap<String, String>() {
        };
        int questionMark = requestPath.indexOf('?');
        if (questionMark != -1) {
            this.requestURI = requestPath.substring(0, questionMark);
            this.queryString = requestPath.substring(questionMark + 1);
            this.parseQueryString(this.queryString);
        } else {
            this.requestURI = requestPath;
        }
    }

    private void parseQueryString(String queryString) {
        String[] nameValuePairs = StringUtils.split(queryString, '&');
        String name, value;
        for (String nameValuePair : nameValuePairs) {
            String[] nvAry = StringUtils.split(nameValuePair, '=');
            name = nvAry[0];
            if (nvAry.length == 2) {
                value = nvAry[1];
            } else {
                value = "";
            }
            queryParameters.add(name, value);
        }
    }

    public void setContentBytes(byte[] content) {
        this.contentBytes = ByteBuffer.wrap(content);
    }

    public void setContentBytes(ByteBuffer content) {
        this.contentBytes = content;
    }

    private int getDefaultServerPort() {
        return 80;
    }

    @Override
    public String getAuthType() {
        return null;
    }

    @Override
    public Cookie[] getCookies() {
        return new Cookie[0];
    }

    @Override
    public long getDateHeader(String name) {
        return 0;
    }

    @Override
    public String getHeader(String name) {
        return headersMap.get(name);
    }

    @Override
    public Enumeration getHeaders(String name) {
        return headersMap.getValuesEnumeration(name);
    }

    @Override
    public Enumeration getHeaderNames() {
        return headersMap.getNamesEnumeration();
    }

    @Override
    public int getIntHeader(String name) {
        return Integer.parseInt(headersMap.get(name));
    }

    @Override
    public String getMethod() {
        return method;
    }

    @Override
    public String getPathInfo() {
        return null;
    }

    @Override
    public String getPathTranslated() {
        return null;
    }

    @Override
    public String getContextPath() {
        return null;
    }

    @Override
    public String getQueryString() {
        return this.queryString;
    }

    @Override
    public String getRemoteUser() {
        return null;
    }

    @Override
    public boolean isUserInRole(String role) {
        return false;
    }

    @Override
    public Principal getUserPrincipal() {
        return null;
    }

    @Override
    public String getRequestedSessionId() {
        return null;
    }

    @Override
    public String getRequestURI() {
        return requestURI;
    }

    @Override
    public StringBuffer getRequestURL() {
        return new StringBuffer(requestURL);
    }

    @Override
    public String getServletPath() {
        return null;
    }

    @Override
    public HttpSession getSession(boolean create) {
        return null;
    }

    @Override
    public HttpSession getSession() {
        return null;
    }

    @Override
    public boolean isRequestedSessionIdValid() {
        return false;
    }

    @Override
    public boolean isRequestedSessionIdFromCookie() {
        return false;
    }

    @Override
    public boolean isRequestedSessionIdFromURL() {
        return false;
    }

    @Override
    public boolean isRequestedSessionIdFromUrl() {
        return false;
    }

    @Override
    public Object getAttribute(String name) {
        return null;
    }

    @Override
    public Enumeration getAttributeNames() {
        return null;
    }

    @Override
    public String getCharacterEncoding() {
        return null;
    }

    @Override
    public void setCharacterEncoding(String characterEncoding) throws UnsupportedEncodingException {

    }

    @Override
    public int getContentLength() {
        return 0;
    }

    @Override
    public String getContentType() {
        return null;
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        if (this.inputStream == null) {
            this.inputStream = new ServletInputStream() {

                @Override
                public int read() throws IOException {
                    if (contentBytes.hasRemaining()) {
                        return contentBytes.get();
                    } else {
                        return -1;
                    }
                }

                @Override
                public int available() throws IOException {
                    return contentBytes.remaining();
                }
            };
        }
        return inputStream;
    }

    @Override
    public String getParameter(String name) {
        checkNeedToParseBody();
        return this.queryParameters.get(name);
    }

    @Override
    public Enumeration getParameterNames() {
        checkNeedToParseBody();
        return this.queryParameters.getNamesEnumeration();
    }

    @Override
    public String[] getParameterValues(String name) {
        checkNeedToParseBody();
        return this.queryParameters.getValuesArray(name);
    }

    @Override
    public Map getParameterMap() {
        checkNeedToParseBody();
        return this.queryParameters.getValuesArrayMap();
    }

    private void checkNeedToParseBody() {
        try {
            ServletInputStream is = getInputStream();
            if (is.available() <= 0) {
                return;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            String string = IOUtils.toString(getInputStream(), "UTF-8");
            this.parseQueryString(string);
        } catch (IOException e) {
            if (logger.isErrorEnabled()) {
                logger.error(e.getMessage(), e);
            }
        }
    }

    @Override
    public String getProtocol() {
        return this.protocol;
    }

    @Override
    public String getScheme() {
        return scheme;
    }

    @Override
    public String getServerName() {
        return this.serverName;
    }

    @Override
    public int getServerPort() {
        return this.serverPort;
    }

    @Override
    public BufferedReader getReader() throws IOException {
        if (reader == null) {
            reader = new BufferedReader(new BufferedReader(new InputStreamReader(getInputStream())));
        }
        return reader;
    }

    @Override
    public String getRemoteAddr() {
        return null;
    }

    @Override
    public String getRemoteHost() {
        return null;
    }

    @Override
    public void setAttribute(String name, Object value) {

    }

    @Override
    public void removeAttribute(String name) {

    }

    @Override
    public Locale getLocale() {
        return null;
    }

    @Override
    public Enumeration getLocales() {
        return null;
    }

    @Override
    public boolean isSecure() {
        return false;
    }

    @Override
    public RequestDispatcher getRequestDispatcher(String path) {
        return null;
    }

    @Override
    public String getRealPath(String path) {
        return null;
    }

    @Override
    public int getRemotePort() {
        return 0;
    }

    @Override
    public String getLocalName() {
        return null;
    }

    @Override
    public String getLocalAddr() {
        return null;
    }

    @Override
    public int getLocalPort() {
        return 0;
    }

}
